package com.netx.generics.time;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import com.netx.generics.basic.Checker;
import com.netx.generics.util.Strings;
import com.netx.generics.basic.IntegrityException;


public class Date implements Formattable, Comparable<Date>, Cloneable {

	public static boolean isLeapYear(int year) {
		Checker.checkMinValue(year, 1, "year");
		return ((year%400==0)||((year%4==0)&&(year%100!=0)));
	}

	public static Date valueOf(String s) {
		return valueOf(s, DateFormat.getDateInstance());
	}

	public static Date valueOf(String s, DateFormat df) {
		Checker.checkEmpty(s, "s");
		Checker.checkNull(df, "df");
		try {
			return new Moment(df.parse(s).getTime()).getDate();
		}
		catch(ParseException pe) {
			throw new DateFormatException("unparseable date: \""+s+"\"");
		}
	}
	
	private static final int[] _days = {
		31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};

	private static java.util.Date _getDate(Date d) {
		return new Moment(d).getCalendar().getTime();
	}

	private int _year;
	private int _month;
	private int _day;

	public Date() {
		this(Calendar.getInstance());
	}

	public Date(int year, int month, int day) {
		// check integrity:
		if(year<1 || month<1 || month>12 || day<1 || day>_days[month-1]) {
			_throwIllegalArgument(year, month, day);
		}
		if(month == 2 && !isLeapYear(year) && day>28) {
			_throwIllegalArgument(year, month, day);
		}
		_year = year;
		_month = month;
		_day = day;
	}

	// for Moment.getDate
 	Date(Calendar c) {
		_year = c.get(Calendar.YEAR);
		_month = c.get(Calendar.MONTH)+1;
		_day = c.get(Calendar.DAY_OF_MONTH);
	}

 	public int getYear() {
 		return _year;
 	}
 	
 	public Date setYear(int year) {
 		if(year < 1) {
			_throwIllegalArgument(year, _month, _day);
 		}
 		_year = year;
 		return this;
 	}

 	public int getMonth() {
 		return _month;
 	}
 	
 	public Date setMonth(int month) {
		if(month<1 || month>12 || _day>_days[month-1] || (month == 2 && !isLeapYear(_year) && _day>28)) {
			_throwIllegalArgument(_year, month, _day);
		}
 		_month = month;
 		return this;
 	}
 	
 	public int getDay() {
 		return _day;
 	}
 	
 	public Date setDay(int day) {
		if(day<1 || day>_days[_month-1] || (_month == 2 && !isLeapYear(_year) && day>28)) {
			_throwIllegalArgument(_year, _month, day);
		}
 		_day = day;
 		return this;
 	}

 	public int compareTo(Date d) {
 		Checker.checkNull(d, "o");
 		return _getDate(this).compareTo(_getDate(d));
 	}
 	
 	public boolean equals(Object o) {
 		if(o == this) {
 			return true;
 		}
 		if(o == null) {
 			return false;
 		}
 		if(o instanceof Date) {
 	 		return compareTo((Date)o)==0;
 		}
 		return false;
 	}

	public boolean after(Date d) {
		return compareTo(d) > 0;
	}

	public boolean before(Date d) {
		return compareTo(d) < 0;
	}

	public String format() {
 		return format(DateFormat.getDateInstance());
 	}
 	
	public String format(DateFormat df) {
		Checker.checkNull(df, "df");
 		return df.format(_getDate(this));
 	}
 	
	public String toString() {
		return format();
	}
	
	public Date clone() {
		try {
			return (Date)super.clone();
		}
		catch(CloneNotSupportedException cnse) {
			throw new IntegrityException(cnse);
		}
	}
	
	private void _throwIllegalArgument(int year, int month, int day) {
		throw new IllegalArgumentException("invalid Date value: "+year+"-"+Strings.valueOf(month, 2)+"-"+Strings.valueOf(day, 2));
	}
}
